package cn.magictodo.weblog.jwt.filter;

import cn.magictodo.weblog.jwt.utils.JwtTokenHelper;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static cn.magictodo.weblog.common.enums.RedisPrefixEnum.REDIS_USER_TOKEN;
import static cn.magictodo.weblog.common.enums.RedisPrefixEnum.REDIS_USER_TOKEN_REFRESH;

/**
 * TokenAuthenticationFilter    校验token令牌
 *
 * @author liu
 * #time 2023-11-17 20:46
 */
@Slf4j
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenHelper jwtTokenHelper;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Value("${jwt.tokenPrefix}")
    private String tokenPrefix;

    @Value("${jwt.tokenHeaderKey}")
    private String tokenHeaderKey;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // 从请求头中获取 key 为 Authorization 的值
        String header = request.getHeader(tokenHeaderKey);

        // 判断 value 值是否以 Bearer 开头
        if (StringUtils.startsWith(header, tokenPrefix)) {
            // 截取 Token 令牌
            String token = StringUtils.substring(header, 7);
            log.info("Token: {}", token);

            // 判空 Token
            if (StringUtils.isNotBlank(token)) {
                // 从 Token 中解析出用户名
                String username = jwtTokenHelper.getUsernameByToken(token);
                log.info("Username: {}", username);
                String redisKey = REDIS_USER_TOKEN.getPrefix() + username;
                Object redisToken = redisTemplate.opsForValue().get(redisKey);

                String refreshKey = REDIS_USER_TOKEN_REFRESH.getPrefix() + username;
                Boolean isRefresh = redisTemplate.hasKey(refreshKey);

//                刷新期间同一用户的请求不再经过认证
                if (Boolean.FALSE.equals(isRefresh)) {
                    //                校验redis中是否有该token
                    if (Objects.isNull(redisToken)) {
                        log.warn("redis中不存在该key对应的token");
                        // 抛出异常，统一让 AuthenticationEntryPoint 处理响应参数
                        authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("Token 已失效"));
                        return;
                    }
                    if(!Objects.equals(token, redisToken)){
                        log.warn("redis中存在该key-token,但是token与redis中的token不一致");
                        authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("已在另一台设备登录"));
                        return;
                    }

                    try {
                        // 校验 Token 是否可用, 若解析异常，针对不同异常做出不同的响应参数
                        jwtTokenHelper.validateToken(token);
                    } catch (SignatureException | MalformedJwtException | UnsupportedJwtException |
                             IllegalArgumentException e) {
                        // 抛出异常，统一让 AuthenticationEntryPoint 处理响应参数
                        authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("Token 不可用"));
                        return;
                    } catch (ExpiredJwtException e) {
                        log.warn("Token 已过期,刷新token");
//                   jwttoken过期 需要无感刷新token
                        String newToken = jwtTokenHelper.generateToken(username);
//                    使用setnx设置刷新token锁，30秒内请求不再认证
                        redisTemplate.opsForValue().setIfAbsent(refreshKey, newToken,30, TimeUnit.SECONDS);
                        response.setHeader("newToken", newToken);
                    }
                }




                if (StringUtils.isNotBlank(username)
                        && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
                    // 根据用户名获取用户详情信息
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                    // 将用户信息存入 authentication，方便后续校验
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                            userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // 将 authentication 存入 ThreadLocal，方便后续获取用户信息
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }

        // 继续执行写一个过滤器
        filterChain.doFilter(request, response);
    }
}
